<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace Dwc\Wxapp;

use Hyperf\Config\Annotation\Value;
use Hyperf\Logger\LoggerFactory;
use Hyperf\Redis\Redis;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Swoole\Coroutine\Http\Client\Exception;
use function Swoole\Coroutine\Http\get;
use function Swoole\Coroutine\Http\post;

/**
 * 微信小程序类.
 */
class MinApp
{
    public string $openid = '';

    /**
     * @var mixed|Redis
     */
    protected Redis $redis;

    /**
     * @var string
     */
    #[Value('cache.default.prefix')]
    protected string $prefix;

    /**
     * @var array $config
     */
    #[Value('wxapp.default')]
    private array $config;

    private ContainerInterface $container;

    /**
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function __construct(ContainerInterface $container, LoggerFactory $loggerFactory, array $config = [])
    {
        if (isset($config['app_id'])) {
            $this->config = $config['app_id'];
        }
        if (isset($config['app_secret'])) {
            $this->config = $config['app_secret'];
        }
        if (isset($config['qrcode_path'])) {
            $this->config = $config['qrcode_path'];
        }
        $this->container = $container;
        $this->redis = $this->container->get(Redis::class);
        $this->logger = $loggerFactory->get('wxapp', 'default');
    }

    public function getAppId(): string
    {
        return $this->config['app_id'];
    }

    public function getAppSecret(): string
    {
        return $this->config['app_secret'];
    }

    public function getQrcodePath(): string
    {
        return $this->config['qrcode_path'];
    }

    public function getOpenid(): string
    {
        return $this->openid;
    }

    /**
     * 获取sessionKey.
     */
    public function getSessionKey(string $code): string
    {
        $url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' . $this->getAppId() . '&secret=' . $this->getAppSecret() . '&js_code=' . $code . '&grant_type=authorization_code';
        $res = get($url);
        $response = $res->getBody();
        $response = json_decode($response, true);
        // 如果返回的数据中包含errcode，则说明获取失败
        if (isset($response['errcode'])) {
            $this->logger->error('获取sessionKey失败', [$response]);
            return '';
        }
        // 设置openid
        $this->openid = $response['openid'];
        return $response['session_key'];
    }

    /**
     * 获取用户信息.
     */
    public function getUserInfo(string $encryptedData, string $iv, string $sessionKey): array
    {
        $pc = new WXBizDataCrypt($this->getAppId(), $sessionKey);
        $data = $pc->decryptData($encryptedData, $iv);
        if ($data === '') {
            $this->logger->error('解密失败', [$encryptedData, $iv, $sessionKey]);
            return [];
        }
        return json_decode($data, true);
    }

    /**
     * 获取缓存.
     */
    public function getCache(string $key): string
    {
        return $this->redis->get($this->prefix . 'wechat.' . $key);
    }

    /**
     * 设置缓存.
     */
    public function setCache(string $key, string $value, int $expire = 0): bool
    {
        return $this->redis->set($this->prefix . 'wechat.' . $key, $value, $expire);
    }

    /**
     * 删除缓存.
     */
    public function delCache(string $key): int
    {
        return $this->redis->del($this->prefix . 'wechat.' . $key);
    }

    /**
     * 获取小程序access_token.
     */
    public function getAccessToken(): string
    {
        $access_token = $this->getCache('access_token');
        if ($access_token) {
            return $access_token;
        }
        $url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $this->getAppId() . '&secret=' . $this->getAppSecret();
        $res = get($url);
        $response = $res->getBody();
        $response = json_decode($response, true);
        // 如果返回的数据中包含errcode，则说明获取失败
        if (isset($response['errcode'])) {
            $this->logger->error('获取access_token失败', [$response]);
            return '';
        }
        // 设置access_token
        $this->setCache('access_token', $response['access_token'], $response['expires_in']);
        return $response['access_token'];
    }

    /**
     * 大量获取小程序二维码, 返回文件名称, 自行拼接所需路径.
     * @param string $scene 场景值
     * @param string $page 小程序页面路径
     * @param string $filename 图片文件名
     * @param int $width 图片宽度,默认430
     * @param string $env_version 体验版为 trial，开发版为 develop
     * @throws Exception
     */
    public function getQrcode(string $scene, string $page, string $filename = 'qrcode.png', int $width = 430, string $env_version = 'release'): string
    {
        $access_token = $this->getAccessToken();
        $url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' . $access_token;
        $data = [
            'scene' => $scene,
            'page' => $page,
            'width' => $width,
            'env_version' => $env_version,
        ];
        return $this->saveFile($url, $data, $filename);
    }

    /**
     * 小量获取小程序码, 返回文件名称, 自行拼接所需路径.
     * @param string $path 场景值
     * @param string $filename 图片文件名
     * @param int $width 图片宽度,默认430
     * @throws Exception
     */
    public function getSmallQrcode(string $path, string $filename = 'qrcode.png', int $width = 430): string
    {
        $access_token = $this->getAccessToken();
        $url = 'https://api.weixin.qq.com/wxa/getwxacode?access_token=' . $access_token;
        $data = [
            'path' => $path,
            'width' => $width,
        ];
        $res = post($url, json_encode($data));
        $response = $res->getBody();
        $resp = json_decode($response, false);
        // 如果返回的数据中包含errcode，则说明获取失败
        if (isset($resp->errcode)) {
            $this->logger->error('获取小程序码失败', [$response]);
            return '';
        }
        $this->createDir($this->config['qrcode_path']);
        // 返回的图片 Buffer，保存为文件
        $file = fopen($this->config['qrcode_path'] . $filename, 'w');
        fwrite($file, $response);
        fclose($file);
        return $filename;
    }

    /**
     * 小程序二维码，返回文件名称, 自行拼接所需路径.
     * @param string $page 小程序页面路径
     * @param string $filename 图片文件名
     * @param int $width 图片宽度,默认430
     * @throws Exception
     */
    public function wxaqrcode(string $page, string $filename = 'qrcode.png', int $width = 430): string
    {
        $access_token = $this->getAccessToken();
        $url = 'https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=' . $access_token;
        $data = [
            'path' => $page,
            'width' => $width,
        ];
        return $this->saveFile($url, $data, $filename);
    }

    /**
     * 保存并返回图片.
     * @throws Exception
     */
    public function saveFile(string $url, array $data, string $filename): string
    {
        $res = post($url, json_encode($data));
        $response = $res->getBody();
        $resp = json_decode($response, false);
        // 如果返回的数据中包含errcode，则说明获取失败
        if (isset($resp->errcode)) {
            $this->logger->error('获取小程序二维码失败', [$response]);
            return '';
        }
        $this->createDir($this->config['qrcode_path']);
        // 返回的图片 Buffer，保存为文件
        $file = fopen($this->config['qrcode_path'] . $filename, 'w');
        fwrite($file, $response);
        fclose($file);
        return $filename;
    }

    /**
     * 创建文件夹.
     */
    public function createDir(string $path): void
    {
        if (! file_exists($path)) {
            mkdir($path, 0777, true);
        }
    }
}
